“类初始化”和“类实例化”严格区分
Java安全[反射(1)]
反射的概念
反射是大多数语言不可少的一部分,
指在程序运行中,
对于任何一个类,都可以通过反射拿到所有属性和方法(包含私有)
,拿到的方法可以调用,总之通过反射,我们可以蒋Java这种静态的语言附加上动态的特性。
反射是指运行时检查和操作类、接口、字段、方法等程序结构的能力。
Java的反射是指程序在运行期可以拿到一个对象的所有信息。
它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。
*Class对象与反射的关系
Class对象
是Java反射机制的入口
,它封装了一个类或接口的运行时信息
。通过
调用Class类的方法
,可以获取这些信息,包括类的构造函数、方法、属性
等。这些信息可以用来
在运行时动态地创建对象、调用方法和访问字段
Class对象与Class的关系
每一个类都有一个对应的Class对象,它封装了该类的运行时信息。
Class对象与实例对象的关系
Class对象表示类运行时的信息,而实例对象则是类的一个具体实例。
每个实例对象都属于一个特定的类,并且与该类的Class对象相关联。
可以通过实例对象来获取其所属类的Class对象,也可通过Class对象来创建实例对象。
*反射例子
1 | public void execute(String className, String methodName) throws Exception { |
上面的例子中,这几个方法包揽了几个在反射中极为重要的方法
获取类的方法:
forName
实例化类对象的方法:
newInstance
从java9开始,建议使用
getDeclaredConstructor().newInstance()
来代替newInstance
方法,因为newInstance
方法会抛出不必要的
InstantiationException
和IllegalAccessException
异常,而且它也不够清晰地表明正在调用构造函数。获得函数的方法:
getMethod
执行函数的方法:
invoke
以上的四种方法几乎包揽所有Java
安全中各种发射有关的payload
Java反射运用实例
1 | import java.lang.reflect.Method; |
运行截图,获取Myclass
类对象,然后调用Myclass
中的方法sayHello
,主函数说明参数类型String.class
并传参John
其中Myclass类没有使用任何访问修饰符(如
public
、private
、protected
),这种访问权限的范围是限定在同一个包(package)中的其他类可以访问它。
Myclass类
可以在src
目录以独立的文件格式存在Myclass.class
但是不能在
src
目录下存在两个相同名字的类,
- 在反射过程中,当你尝试通过
Class.forName("MyClass")
这样的方式加载类时,类加载器会在类路径(包括src
目录等)中查找MyClass
对应的.class
文件来加载,如果没有足够的信息(如包名)来区分应该加载哪一个MyClass
,就会导致报错。在进行反射时会发生报错:
类重复
获取类的三个方法
通常我们有三种方法
获取一个”类”,也就是java.lang.Class
对象
forName
不是获取”类”的唯一途径。
obj.getClass()
如果上下文存在某个实例
obj
,那么可以通过obj.getClass()
来获取它的类
1
2
3
4
5
6
7
8
9
10
11 public class ReflectionExample {
public static void main(String[] args) {
MyClass obj = new MyClass();
Class<?> cls = obj.getClass();
System.out.println("Class Name: " + cls.getName());
}
}
class MyClass {
// 类的定义
}一个
obj
对象MyClass
实例两个
obj
对象MyClass
和YourClass
实例
.class[不属于反射]
如果你已经加载了某个类,只是想获取它的
java.lang.Class
对象,那么就直接拿它的class
属性即可。这个方法其实并不属于反射。
1
2
3
4
5
6
7
8
9
10 public class ReflectionExample {
public static void main(String[] args) {
Class<?> cls = Test.class;
System.out.println("Class Name: " + cls.getName());
}
}
class Test {
// 类的定义
}
Class.forName()
如果你知道某个类的名字,想获得到这个类,就可以用
forName
来进行获取
1
2
3
4
5
6
7
8
9
10
11 public class ReflectionExample {
public static void main(String[] args) throws ClassNotFoundException {
String className = "MyClass";
Class<?> cls = Class.forName(className);
System.out.println("Class Name: " + cls.getName());
}
}
class MyClass {
// 类的定义
}
反射的作用
getClass().forName("java.lang.Runtime")
在安全研究中,我们使用反射的一大目的,就是绕过某些沙盒。
比如,上下文中如果只是Integer
类型的数字,我们如何获取到可以执行命令的Runtime
类呢?
java.lang.Runtime
类是一个与 Java 运行时环境相关的类,它提供了访问 Java 运行时系统的接口,例如执行外部命令、管理内存等功能。这个类采用了单例模式,也就是整个 Java 应用程序在运行期间通常只有一个Runtime
实例存在。
也许可以这样(伪代码):
1 | 1.getClass().forName("java.lang.Runtime") |
这个代码看不懂,找了几篇文章,下面是个人理解:
这段代码是非常常见的payload格式,在很多服务器报警和CVE中常见,下面是一个对应漏洞分析文档
http://exploit-db.com/docs/english/46303-remote-code-execution-with-el-injection-vulnerabilities.pdf
常见的格式应该是这种
*.getClass().forName("java.lang.Runtime").getRuntime().exec()
简单解析一下,
*
为某实例化对象,getClass获取其类对象,Class.forName是对类进行加载,getRuntime可获得这个代表运行时环境的实例对象,以便后续进行如执行外部命令等操作。文档中对该
payload
进行了展示(经典弹计算器对
Runtime
类,执行命令的功能效果,实例代码执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import java.io.IOException;
public class CommandExecution {
public static void main(String[] args) {
try {
// 执行命令
Process process = Runtime.getRuntime().exec("calc");
// 等待命令执行完成
process.waitFor();
// 获取命令的退出值
int exitValue = process.exitValue();
System.out.println("命令执行完成,退出值:" + exitValue);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
forName重载/加载/初始化
可以看看菜鸟教程或者其他文章
Java class.forname 详解 | 菜鸟教程 (runoob.com)
【Java核心技术】类型信息(Class对象 反射 动态代理) - luoxn28 - 博客园 (cnblogs.com)
forName
有两个函数重载:
Class<?> forName(String name)
Class<?> forName(String name, boolean initialize, ClassLoader loader)
第一个就是我们最常见的获取class
的方式,其实可以为第二种方式的一个封装
1 | Class.forName(className) |
默认情况下,
forName
的第一个参数是类名
forName
的第二参数表示是否初始化
第二个参数initialize
常常被人误解,使用功能”.class”来创建Class对象的引用时
.class
和forName()
在Java中的区别是:
.class
是在编译时确定的,不会触发类的初始化,只是获取类的Class
对象forName()
是在运行时动态加载类,并且会触发类的初始化,除非指定initialize
为false
p.s.初始化类和创建类对象的区别
初始化类和创建类对象的区别是:
- 初始化类是指
加载类的字节码到内存中
,并执行类的静态变量
和静态代码块
。初始化类只会发生一次
,仅仅当类被首次使用时。- 创建类对象是指使用类的
构造方法来分配内存空间
,并给对象的属性赋值
。创建类对象可以发生多次
,每次都会返回一个新的对象
。
所以第二参数即使为initialize=true
,这里指的也是初始化类而非创建类对象,所以不会触发构造函数
forName()
通过这个例子,
可以看到初始化的作用和创建类对象,
第一次
初始化
时,加载
静态变量和静态代码块,打印数据
1
2 2
static block第二次
初始化
时,不加载
静态变量和静态代码块,无任何打印数据当
创建类对象
时,加载类中构造函数,如果多次创建类对象,会执行多次构造函数
.class
可以看到.class
不会进行
初始化静态变量
和静态代码块
,也不会创建类对象
常见有三种初始化
1 | public class TrainPrint { |
这三种中,
最先调用的是
static{}
然后是单个中
{}
最后是
构造函数
运行便可知,
因为
static{}
是类初始化时调用的,而{}
中代码是放在构造函数super()后面,也就是构造函数
的前面
==>
forName
中initialize=true
其实就是告诉Java虚拟机是否执行类初始化
恶意调用
假如我们有如下函数,其中函数的参数name可控:
1 | public void ref(String name) throws Exception { |
我们再构造一个恶意类,将恶意的代码放置在static{}
中,从而执行
1 | import java.lang.Runtime; |
当然,这个恶意类如何带入目标机器中,可能就涉及到ClassLoader
的一些利用方法
后面学到会补充这里实现传入的过程
forName
的第三个参数ClassLoader
ClassLoader
是什么呢?它就是一个”加载器”,告诉Java虚拟机如何加载这个类。
Java中默认的ClassLoader
就是根据类名来加载类,这个类名是类的完整路径,如java.lang.Runtime
。